package cn.js189.log.aop;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.expression.ExpressionUtil;
import cn.hutool.json.JSONUtil;
import cn.js189.common.constants.Constants;
import cn.js189.common.domain.SysLogNodes;
import cn.js189.common.util.IpUtils;
import cn.js189.common.util.ServletUtils;
import cn.js189.common.util.annotation.LogPlus;
import cn.js189.common.domain.LogBase;
import cn.js189.log.service.SysLogService;
import cn.js189.system.api.domain.SysLogPlus;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

/**
 * 获取方法内部请求第三方接口日志
 */
@Aspect
@Component
public class LogPlusAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogPlusAspect.class);

    private static final String RET_CODE = "code";
    
    private static final String RET_MSG = "msg";

    @Resource
    private SysLogService sysLogService;

    @Value("${spring.application.name}")
    private String serviceName;
    
//    @Resource
//    private RedisCache redisCache;

    private static final String SERVICE_ID = "SERVICE_ID:";

    /**
     * 定义切入点
     *
     * @param log 日志增强注解
     */
    @Pointcut("@annotation(log)")
    public void pointcut(LogPlus log) {

    }

    /**
     * 环绕通知 进入方法前执行
     *
     * @param point 织入点
     * @param log   日志增强注解
     * @return 结果
     * @throws Throwable 异常
     */
    @Around("@annotation(log)")
    public Object around(ProceedingJoinPoint point, LogPlus log) throws Throwable {
        handleCLog(point, log);
        return point.proceed();
    }
    
    /**
     * 前置事件处理
     * @param point 切入点
     * @param log 日志注解
     */
    protected void handleCLog(final ProceedingJoinPoint point, LogPlus log) {
        // 获取请求参数
        LogBase logBase = new LogBase();
        logBase.setParams(JSONUtil.toJsonStr(getParamMap(point)));
        logBase.setUseTime(System.currentTimeMillis());
        logBase.setRequestTime(DateUtil.formatDateTime(DateUtil.date()));
        // 获取当前线程中日志信息
        Map<String, LogBase> logMap = this.getLogMap();
        // 主接口日志
        if (MapUtil.isEmpty(logMap)) {
            logMap = new LinkedHashMap<>(1);
            logBase.setType(Constants.MASTER);
            logBase.setMethod(point.getTarget().getClass().getName());
            logBase.setPath(ServletUtils.getReqPath(ServletUtils.getRequest()));
        } else {
            // 获取请求接口地址
            Object value = this.convertEl(point, log.url());
            // 获取请求接口编码
            Object name = this.convertEl(point, log.interCode());
            // 节点类型
            logBase.setType(Constants.SLAVE);
            // 生产还是测试环境
            logBase.setIsTest(getEnvironment());
            // 请求方式
            logBase.setMethod(log.type());
            // 接口编码
            logBase.setName(String.valueOf(name));
            // 接口提供方
            logBase.setOwner(log.owner());
            // 接口地址
            logBase.setUrl(String.valueOf(value));
        }
        logMap.put(this.getKey(point), logBase);
        this.setLogMap(logMap);
    }

    /**
     * 环绕通知 - 方法执行后执行
     *
     * @param point      织入点
     * @param log        日志增强
     * @param jsonResult 日志返回参数
     */
    @AfterReturning(pointcut = "@annotation(log)", returning = "jsonResult")
    public void afterReturning(JoinPoint point, LogPlus log, Object jsonResult) {
        handlerAfter(point, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param point 切点
     * @param e     异常
     */
    @AfterThrowing(value = "@annotation(log)", throwing = "e")
    public void doAfterThrowing(JoinPoint point, LogPlus log, Exception e) {
        if (e != null && e.getMessage() != null) {
            String logKey = this.getKey(point);
            // 获取当前线程中日志信息
            Map<String, LogBase> logMap = this.getLogMap();
            // 日志处理
            if (logMap.containsKey(logKey)) {
                LogBase logBase = logMap.get(logKey);
                // 判断返回值中的方法名称如果是线程执行的第一个方法的名称则 认为方法执行结束 整合日志删除日志记录
                // 保存主方法日志
                SysLogPlus sysLog = getLogInfo(logBase, null);
                sysLog.setResult(e.getMessage());
                sysLog.setCode(500);
                sysLog.setMsg(e.getMessage());
                List<SysLogNodes> listNew = new ArrayList<>(1);
                for (Map.Entry<String, LogBase> map : logMap.entrySet()) {
                    LogBase logB = map.getValue();
                    SysLogNodes nodes = BeanUtil.toBean(logB, SysLogNodes.class);
                    listNew.add(nodes);
                }
                sysLog.setLogList(JSONUtil.toJsonStr(listNew));
                // 保存日志信息删除缓存key
                this.saveLogAndRemoveKey(sysLog);
            }
        }
    }
    
    /**
     * 后置事件处理
     * @param point 切入点
     * @param retObj 返回结果
     */
    public void handlerAfter(JoinPoint point, Object retObj) {
        // 存储当前方法相关数据key
        String logKey = this.getKey(point);
        // 获取当前线程中日志信息
        Map<String, LogBase> logMap = this.getLogMap();
        LogBase msLog = logMap.get(logKey);
        // 结果
        String jsonStr = JSONUtil.toJsonStr(retObj);
        // 日志处理
        msLog.setResult(jsonStr);
        msLog.setResponseTime(DateUtil.formatDateTime(DateUtil.date()));
        msLog.setUseTime(System.currentTimeMillis() - msLog.getUseTime());
        //
        logMap.put(logKey, msLog);
        setLogMap(logMap);
        // 判断返回值中的方法名称如果是线程执行的第一个方法的名称则 认为方法执行结束 整合日志删除日志记录
        if (Constants.MASTER.equals(msLog.getType())) {
            // 保存主方法日志
            SysLogPlus sysLog = getLogInfo(msLog, retObj);
            List<SysLogNodes> listNew = new ArrayList<>(1);
            for (Map.Entry<String, LogBase> map : logMap.entrySet()) {
                LogBase value = map.getValue();
                if (Constants.SLAVE.equals(value.getType())) {
                    SysLogNodes nodes = BeanUtil.toBean(value, SysLogNodes.class);
                    listNew.add(nodes);
                }
            }
            sysLog.setLogList(JSONUtil.toJsonStr(listNew));
            // 保存日志信息
            // TODO 后续替换redis存储
            this.saveLogAndRemoveKey(sysLog);
        }

    }

    /**
     * 日志数据组装
     *
     * @param msLog  日志对象
     * @param retObj 返回结果
     */
    public SysLogPlus getLogInfo(LogBase msLog, Object retObj) {
        // 保存主方法日志
        SysLogPlus sysLog = new SysLogPlus();
        // 结果
        if (retObj != null) {
            String jsonStr = JSONUtil.toJsonStr(retObj);
            JSONObject ret = new JSONObject();
            if (JSONUtil.isTypeJSON(jsonStr)) {
                ret = JSON.parseObject(jsonStr);
            } else {
                ret.put(RET_CODE, "200");
                ret.put(RET_MSG, "请求成功");
            }
            sysLog.setCode(ret.getInteger(RET_CODE));
            sysLog.setMsg(CharSequenceUtil.isNotBlank(ret.getString(RET_MSG)) ? ret.getString(RET_MSG) : "请求成功");
            sysLog.setResult(jsonStr);
        }

        sysLog.setLogId(IdUtil.fastSimpleUUID());
        sysLog.setIp(IpUtils.getIpAddr());
        sysLog.setMethod(msLog.getMethod());
        sysLog.setPath(msLog.getPath());
        sysLog.setParam(msLog.getParams());
        sysLog.setRequestTime(msLog.getRequestTime());
        sysLog.setResponseTime(msLog.getResponseTime());
        sysLog.setUseTime(msLog.getUseTime());
        return sysLog;
    }

    /**
     * 保存当前方法获取key
     */
    public String getKey(JoinPoint point) {
        return SecureUtil.md5(CharSequenceUtil.format("{}#{}#{}#{}", SERVICE_ID, serviceName, String.valueOf(point.getSignature())));
    }

    /**
     * 获取主方法key
     *
     * @return 结果
     */
    public String getThreadKey() {
        return ServletUtils.getSession().getId() + Thread.currentThread().getId();
    }

    /**
     * el表达式解析
     */
    public Object convertEl(JoinPoint point, String value) {
        if (CharSequenceUtil.isBlank(value)) {
            return "";
        }
        try {
            return ExpressionUtil.eval(value, getParamMap(point));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return value;
        }
    }

    /**
     * 获取生产还是测试环境
     */
    public String getEnvironment() {
        return "测试";
    }
    
    /**
     * 参数转换
     */
    public Map<String,Object> getParamMap(JoinPoint point){
        // 参数转换
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 设置方法参数作为变量
        Object[] args = point.getArgs();
        String[] parameterNames = signature.getParameterNames();
        Map<String, Object> paramsMap = new HashMap<>(args.length);
        for (int i = 0; i < args.length; i++) {
            // 将参数放入上下文
            paramsMap.put(parameterNames[i], args[i]);
        }
        return paramsMap;
    }
    
    /**
     * 获取存储的日志内容
     * @return 日志对象
     */
    public Map<String,LogBase> getLogMap(){
/*
        String data = redisCache.get(this.getThreadKey());
        return BeanUtil.beanToMap(data);
*/
	    return Constants.cLog.getOrDefault(this.getThreadKey(),new LinkedHashMap<>(1));
    }
    
    /**
     * 获取存储的日志内容
     * @param threadKey
     * @return 日志对象
     */
    public void setLogMap(Map<String,LogBase> value){
	    /* redisCache.set(this.getThreadKey(),value); */
        Constants.cLog.put(this.getThreadKey(), value);
    }
    
    /**
     * 删除对应key值的日志
     */
    public void saveLogAndRemoveKey(SysLogPlus sysLog){
        sysLogService.saveLog(sysLog);
        Constants.cLog.remove(this.getThreadKey());
    }
    
}
